//StaticModel.h
//Created 01/12/13
//Created By Daniel Bowler
//
//Contains rendering data (VB, IB) and materials (inc textures) for a static
//model. In addition, retians a system memory (RAM) copy of the vertex
//data and will automatically create an AABB for frustum culling use
//and basic collision testing.
//
//These models can move around the scene but are not animated. 
//
//Supoorts basic models (boxes, spheres, grids etc) and obj models loaded
//with my custom loader.
//
//In addition, the class has been extended to support ASSIMP (Open asset import
//library) - obj, 3ds etc... everything we will need :D - Static Models will
//only extract mesh data - if you need animated models, use AnimatedModel
//instead. 
//
//Once a model has been inited, it can not be changed. 

#pragma once

//Include vector - this vector holds the system memory copy of vertex data
#include <vector>

//Need XNA and D3D11 libs
#include "d3dUtil.h"
#include "xnacollision.h"

//Need GeometryGenerator for the MeshData struct. In .cpp, used to 
//create grids, boxes, etc. 
#include "GeometryGenerator.h"

//for debugging
#include <sstream>

//Forward declare graphics interface so we can make it a friend. 
class GraphicsInterface;
//Forward declare OBJ Loader.
class OBJLoader;

//Forward declare assimp structures
struct aiScene;
struct aiMaterial;
struct aiMesh;

//Structure that is used to tell D3D how to tessellate geometry when rendering
//with displacement mapping. 
struct TessellationData
{
	TessellationData()
	{
		//Init to something useful as a default.
		//
		//When we sample the heightmap (displacement map) in side the tessellation DS stage, 
		//we get a value between [0.0f, 1.0f] (originally it was an 8bit UINT. Therefore
		//displacement mapping isnt all that accurate anyway)
		//. To get the amount to displace each vertex, we
		//multiply HeightScale by the sampled value. Thus, if we sample 1.0f and height scale 
		//is 0.07f, we displace the vertex by 0.07 world space units. This is about right
		//for cobbles/bricks, etc. 
		//
		//A large value here looks awful given that 1 unit in world space is actually fairly
		//large and the displacement map can only shift verticies by 256 differnt heights. 
		HeightScale = 0.07f;

		//Controls LOD essentially.
		MaxTessDistance = 15.0f;
		MinTessDistance = 30.0f;
		//Use the smallest values you can possibly get away with. 
		MaxTessFactor = 15.0f;
		MinTessFactor = 7.5f;
	}

	//How exagerated the displacement of verticies is.
	float HeightScale;
	//Distance from the camera that the maximum tessellation factor will be achieved.
	float MaxTessDistance;
	//Distance from the camera that the minumum tessellation factor will be achieved. 
	float MinTessDistance;
	//Max tess factor. IE, how many times to divide the geometry up.
	float MaxTessFactor;
	//Min tess factor. As above. 
	float MinTessFactor;
};

//Structure that describes where to load textures, normal maps and
//displacement maps from. Used when you create a box, sphere, grid etc. 
struct StaticModelBasicMaterial
{
	StaticModelBasicMaterial()
	{
		//Init strings
		TextureMapFilePath = L"";
		NormalMapFilePath = L"";
		HeightMapFilePath = L"";

		//Init flags to false
		UseTextureMap = false;
		UseNormalMap = false;
		UseSeperateHeightMap = false; //If true, .a component of nm is the height map. 

		//Uses the normal map texture (Alpha component for height map)!. Good for walls/floors!
		//VERY expensive... please use sparingly. Avoid boxes, spheres, geospheres. Works
		//best on grids. Also, tessellate sparingly. Make sure to have some LOD settings. 
		//
		//Not all normal maps contains a height map (Displacement map). I think viewing the texture
		//in the DirectX Texture tool (.DDS especially)
		//will tell you if it has a height map. It should be stored
		//in the alpha component of the texture. 
		//
		//Failing that, open it inside a good image editor (not paint...) and have a look. 
		UseDisplacementMap = false;

		//Default as opaque geometry
		IsOpaque = true;

		//No tex scaling
		TexScale = XMFLOAT2(1.0f, 1.0f);

		//Init Material
		Material.Ambient = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
		Material.Diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
		Material.Reflection = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);
		Material.Specular = XMFLOAT4(1.0f, 1.0f, 1.0f, 64.0f);
	}

	//Badic lighting material. Ambient, diffuse, specular. reflection is used
	//for reflection mapping. diffuse.a is the opacity of the object when alpha 
	//blended transparancy is used. specular.w is the specular power.
	Material Material;

	//File paths including the file type. Eg:
	//L"Textures/bricks.dds". One texture for the box, sphere etc. 
	LPCWSTR TextureMapFilePath;
	LPCWSTR NormalMapFilePath; //Also the displacement map if you use displacement mapping.
	LPCWSTR HeightMapFilePath; //Seperate height map file. (Optional)

	//Must!!! Set these.
	bool UseTextureMap; //If false, draws model white. No normal/disp map. 
	bool UseNormalMap;
	bool UseDisplacementMap; //If this is true, then it overrides normal mapping.(only if supported by texture)
	bool UseSeperateHeightMap; //If false, nm.a is the height map. Else, hm.r is sampled


	//If using Displacement mapping, fill this out (Experiment)
	TessellationData TessData;

	//If false, object will be rendered with alpha blending. To set transparancy
	//set Material::Diffuse.a. Default to true.
	bool IsOpaque;

	//Tex transformation - Works great on grids, but rather poorly
	//on boxes. If you want to properly tile textures on boxes, use
	//the InstancedModel instead and draw as many boxes as you need each
	//with there own tex transformation matrix. 
	XMFLOAT2 TexScale;
};

class StaticModel
{
	//So the graphics interface can access private data - semi-hack, but
	//it does reduce the number of getters this class has to have. 
	friend GraphicsInterface;
	//Also OBJ Loader so it can have access to the private data aswell.
	//Coupling issues yes, but this is just easier for me to develop. 
	friend OBJLoader;
public:
	//Standard constructor/destructor. 
	StaticModel();
	~StaticModel();

	//Sets the world matrix data. 
	void SetWorldPosition(XMFLOAT3 pos);
	void SetWorldRotation(XMFLOAT3 rot);
	//Note: Make sure that the x, y and z scale factors are the same. Otherwise, 
	//frustum culling causes a crash. I may look in to fixing this at somepoint. 
	void SetWorldScale(XMFLOAT3 scale);
	void SetWorldMatrix(XMFLOAT3 pos, XMFLOAT3 rot, XMFLOAT3 scale);
	//Gets the world matrix data
	XMFLOAT3 GetWorldPosition(){return position;};
	XMFLOAT3 GetWorldRotation(){return rotation;};
	XMFLOAT3 GetWorldScale(){return scaling;};

	//Gets the models AABB in local space
	XNA::AxisAlignedBox GetAABBL(){return aabbL;};
	//Calculates world space AABB
	XNA::AxisAlignedBox CalculateWorldSpaceAABB();

	//Reutns OABB in initial state (local space)
	XNA::OrientedBox GetOABBL(){return oabb;};
	//Returns OABB in world space. 
	XNA::OrientedBox CalculateWorldSpaceOABB();

	//Sets the shadow casting state. If true (default), then the model will
	//cast shadows. If false, the model will not. 
	void SetIsShadowCaster(bool state){isShadowCaster = state;};
	//Sets the SSAO Map sampling state. If true (default), then we will sample
	//the SSAO Map. If false, we will not. 
	void SetShouldSampleSSAOMap(bool state){shouldSampleSSAOMap = state;};

	//Sets tessellation factor manually. Used for models loaded through assimp. 
	//Every submodel will share the same tessellation factors. 
	void SetAssimpModelsTessellationData(TessellationData& tessData);
	//Functions which set the submodel materials. 
	//
	//Firstly, updates all of the submodels materials with the same material
	void SetSubmodelsWithSameMaterial(Material& material);
	//Secondly, sets indivdual submodel (index) material.
	void SetSubModelMaterial(unsigned index, Material& material);
	//Thirdly, set reflection property for all submodels.
	void SetSubmodelsWithSameReflectionData(XMFLOAT3 &reflectivity);

	//Creates standard geometry using the geometry generator. Play around with sliceCount, stackCount
	//subDivisions and numVerticiesN/M
	//- Essentially tessellates the geometry (When created. Not dynamic tessellation)
	void CreateBox(float width, float height, float depth,
		StaticModelBasicMaterial* material,
		ID3D11Device* device);
	void CreateSphere(float radius, unsigned sliceCount, unsigned stackCount,
		StaticModelBasicMaterial* material,
		ID3D11Device* device);
	void CreateGeoSphere(float radius, unsigned subDivisions, 
		StaticModelBasicMaterial* material,
		ID3D11Device* device);
	void CreateCollum(float bottomRadius, float topRadius, float height,
		unsigned sliceCount, unsigned stackCount, 
		StaticModelBasicMaterial* material,
		ID3D11Device* device);
	//Note: grids are 2D objects essentially. If you create one and cant seem to find
	//it, remember that backface culling is enabled. 
	//
	//Displacement mapping looks good with grids. A good idea is to have floors with
	//subtle displacement mapping (if normal mapping wont cut it), or walls. Whilst it
	//would be possible to use a grid and a displacement map to have rocks around the edges
	//of a map, the displacement map has a fairly low resolution - thus, it will look a bit (very)
	//shit indeed.
	//
	//If possible: When using a displacement map, try and pre tessellate the geometry by setting a
	//higher numVerticiesM/N value here and a low TessellationData::MaxTessFactor (Ideally 1). 
	//This will add time it takes to load the geometry and vram allocation for the vertex buffer
	//but will cost less to render.
	void CreateGrid(float width, float height, 
		unsigned numVerticiesM, unsigned numVerticiesN, 
		StaticModelBasicMaterial* material,
		ID3D11Device* device);

	//Loads an obj file (through my OBJLoader). Get material data from a .mtl file 
	void CreateModelFromOBJFile(
		ID3D11Device* device, 
		LPCWSTR objFilePath,					     //Eg: L"Models/myFile.obj". Vertex data file
		std::vector<LPCWSTR> &objMaterialFilePath,	 //Eg: L"Models.myFile.mtl". Material data file (Array, can be multiple .mtl files)
		std::string textureFolderPath,				 //Eg: "Textures/". Where the textures have been placed. 
		bool useDisplacementMapping,	             //Will override normal mapping in favour of disp mapping for supported materials
		TessellationData* tessData = 0,	             //Only used when displacement mapping is used. 
		XMFLOAT2 texScale = XMFLOAT2(1.0f, 1.0f));   //Texture Scaling Matrix.

	//Loads a file with Assimp. Assimp will load all sorts of formats - obj, 3ds, blend etc
	//(See Assimp documentation for a list of supported formats). This version will
	//extract mesh data (and material) only - animation data will be ignored. If you want
	//an animated model, use the AnimatedModel class instead. 
	//
	//This works an awful lot better than my own OBJ loader (though, more expensive to 
	//load) since it can do an awful lot of post processing to the mesh data. I strongly
	//suggest you use this over mine at all times (Except perhaps one obj model... it would be nice
	//to be able to get some marks for the loader - it did take an age to implement!)
	//
	//When passing the filepath, ensure to include the file extention (type) and directory.
	//
	//Texture directory is the folder where the textures are placed. Eg: "Textures/"
	//
	//The 3 bools - Sometimes a model will look wrong. This is down to the way the
	//modeler has developed the asset (ie, in right hand coord system rather than left
	//hand). Change these bools if the model looks wrong (trial and error I'm afraid. If things
	//do not look correct after messing about with the flags, please find me!)
	//
	//assimp.sourceforge.net/lib_html/postprocess_8h.html#a64795260b95f5a4b3f3dc1be4f52e410a8857a0e30688127a82c7b8939958c6dc
	//
	//IMPORTANT: shouldFixInwardFacingNormals will invert normals that assimp deems to be inward
	//facing - Sometimes we want this on, but for the absolute vast majoirty of models, we want
	//this OFF!
	//
	//Also note: All 3D models are constructed in a local space defined by the artist. This coordinate
	//system is convinient for them to work in. This often means a model will be HUGE. Therefore, you
	//will need to scale the model down by a fair bit. (1000th of the size compared to when loaded
	//isnt uncommon)
	//
	//Note: loadDisplacementMapIfAvailable flag - if a model has a displacement map, set this
	//flag to true to use it. However, it may not actually produce good results so an option
	//has been added to disable displacement mapping for an entire model. 
	void CreateModelThroughAssimp(ID3D11Device* device, 
		std::string& file, std::string& textureDirectory, 		
		bool shouldMakeLeftHanded = true, bool shouldFlipUV = true, bool shouldFlipWinding = true,
		bool shouldFixInwardFacingNormals = false, 
		bool loadDisplacementMapIfAvailable = false);

private:
	//Internal structure that represents a sub portion of a model (can also be the entire model - 
	//especially in the case of the simple geometry like boxes, grids etc)
	struct SubModelData
	{
		SubModelData()
		{
			vb = 0;
			ib = 0;
			TextureMap = 0;
			NormalMap = 0;
			HeightMap = 0;

			//Default topology is triangle list (basic models use this, some
			//.obj may use quads
			Topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;

			StartDrawIndex = 0;
			DrawCount = 0;

			//Opaque objects.
			IsOpaque = true;

			//Initially not using displacement mapping.
			IsUsingDisplacementMapping = false;
			IsUsingSeperateHeightMap = false;

			//Init tex transform matrix.
			XMMATRIX i = XMMatrixIdentity();
			XMStoreFloat4x4(&TexTrans, i);
		}
		~SubModelData()
		{
			ReleaseCOM(vb);
			ReleaseCOM(ib);
			ReleaseCOM(TextureMap);
			ReleaseCOM(NormalMap);
			ReleaseCOM(HeightMap);
		}

		//Rendering data: Vertex buffer and optional index buffer. Per material
		//vb and ib (it is possible for multiple materials to share the same
		//vb / ib... but just incase the model fails to provide say tex coords for
		//a subsection of the model, the option to use individual vb/ib has been given)
		ID3D11Buffer* vb;
		ID3D11Buffer* ib;

		//Default topology is triangle list (basic models use this, some
		//.obj may use quads
		D3D11_PRIMITIVE_TOPOLOGY Topology;

		//Texture map
		ID3D11ShaderResourceView* TextureMap;
		//Normal map
		ID3D11ShaderResourceView* NormalMap;
		//Optional disp map
		ID3D11ShaderResourceView* HeightMap;
		
		//Flag indicating if this material uses displacement mapping. Makes
		//use of the normal map for this feature.
		bool IsUsingDisplacementMapping;
		bool IsUsingSeperateHeightMap;

		//Tessellation data for use with displacement mapping. 
		TessellationData TessData;

		//Lighting material
		Material Mat;

		//Number of indicies or verticies for this portion of the
		//model
		UINT StartDrawIndex;
		UINT DrawCount;

		//Bool indcating if this object is trasnaprant or opaque. IMPORTANT
		//otherwise lighting will be all messed up.
		bool IsOpaque;

		//Tex transformation matrix.
		XMFLOAT4X4 TexTrans;
	};

private:
	//flag indicating if a model has been created - ie, we have loaded
	//some vertex data
	bool isInit;

	//Flag indicating if this model is a shadow casting model. Initially
	//set to true. Very(!!) high polly moddels probably shouldnt cast shadows
	//(EG: Terrain).
	bool isShadowCaster;
	//Flag indicating if the model should sample the SSAO map. Initially true, 
	//but the effect looks awful on the terrain models from QT (Large models
	//in terms of world space volume, low poly count)
	bool shouldSampleSSAOMap;

	//World matrix - essentially moves and orientates all verticies of a mesh.
	XMFLOAT4X4 worldMatrix;
	//Translation, rotation and scaling factors that make up the world matrix.
	XMFLOAT3 position;
	XMFLOAT3 rotation;
	XMFLOAT3 scaling;

	//AABB - Useful for collision detection. Remember to transform AABBs to the
	//same space. (Usually world space). AABB is before multiplying by any
	//world matrix. Thus, if you have scaled objects, its going to be very wrong in
	//its current form. 
	XNA::AxisAlignedBox aabbL;

	//OABB for collision
	XNA::OrientedBox oabb;

	//Total draw count - total number of indicies or verticies to draw all meterials. 
	//Maybe useful for the ZPrePass to cut down on Draw() calls (IE, objects that 
	//have several materials will be rendered in one z pre pass stage). 
	UINT totalDrawCount;

	//System memory copy of vertex data (Only Position data)
	std::vector<XMFLOAT3>systemVertexCopyArr;

	//Materials for this model - includes loaded textures and vb/ib.
	std::vector<SubModelData*>subModels;

private:
	//Loads vertex and index buffer for simple object. Also handles the material.
	//(Grids, boxes etc)
	void LoadSimpleModelData(ID3D11Device* device, 
		StaticModelBasicMaterial* material,
		GeometryGenerator::MeshData& data);

	//Loads a submodel through assimp. Once done, puts (back) the submodel
	//on to our vector, adds verticies to the system vertex copy, and updates totalDrawCount
	void LoadSubModelThroughAssimp(ID3D11Device* device, const aiScene* scene, unsigned meshIndex, 
		std::string& textureDir, bool loadDispMap);
	//Loads material data for a mesh (Sub Model)
	void LoadAssimpMaterial(ID3D11Device* device, aiMaterial* material, SubModelData* subModel,
		std::string &textureDir, bool loadDispMap);
	//Loads vertex data for a mesh (Sub Model) - also index data. Note, also fills the 
	//system copy of positions for use when we create our AABB. 
	void LoadAssimpVertexData(ID3D11Device* device, aiMesh* mesh, SubModelData* subModel);

	//Uses D3DX to load a texture. ToWS converts from std::string to wide string which
	//is reuired by D3DX
	std::wstring ToWS(std::string s);
	void LoadTexture(ID3D11Device* device, std::string& file, ID3D11ShaderResourceView** srv);

	//Creates world matrix.
	void CreateWorldMatrix();

	//Creates the AABB - used for frustum culling by the GraphicsInterface.
	//
	//Update: Also generates OABB.
	void CreateAABB();
};